package uk.ac.lkl.lpp.model;



import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Map;
import java.util.TreeMap;

import javax.swing.table.AbstractTableModel;

import uk.ac.lkl.common.util.ds.AbstractNotifyingList;
import uk.ac.lkl.common.util.ds.event.ListEvent;
import uk.ac.lkl.common.util.ds.event.ListListener;

import uk.ac.lkl.lpp.model.ldo.TeachingMethod;


public class TeachingMethodTableModel extends AbstractTableModel {

    private static final long serialVersionUID = 1L;


    /**
     * Map from field group to the base of the columns it refers to.
     * 
     */
    private TreeMap<FieldGroup<TeachingMethod>, Integer> fieldGroupMap;


    private AbstractNotifyingList<TeachingMethod> teachingMethods;


    private PedagogyPlanModel model;


    public TeachingMethodTableModel(PedagogyPlanModel model) {
        this.model = model;
        teachingMethods = model.getList("selected", TeachingMethod.class);
        initialiseFieldGroups();
        addListeners();
    }


    public PedagogyPlanModel getPedagogyPlanModel() {
        return model;
    }


    private void initialiseFieldGroups() {
        ArrayList<FieldGroup<TeachingMethod>> fieldGroups = new ArrayList<FieldGroup<TeachingMethod>>();

        // todo: clean up fact that some are constructed using list,
        // others using model.
        fieldGroups.add(new TeachingMethodListFieldGroup(teachingMethods));
        fieldGroups.add(new TeachingMethodSpecifiedHoursFieldGroup(model));
        fieldGroups.add(new TeachingMethodCognitiveActivitiesFieldGroup(model));
        fieldGroups.add(new TeachingMethodGroupSizeFieldGroup(model));
        // new TeachingMethodPreparationTimeFieldGroup(model)

        fieldGroupMap = new TreeMap<FieldGroup<TeachingMethod>, Integer>();

        int base = 0;
        for (FieldGroup<TeachingMethod> fieldGroup : fieldGroups) {
            fieldGroupMap.put(fieldGroup, base);
            base += fieldGroup.size();
        }
    }


    private void addListeners() {
        addListListener();
    }


    private void addListListener() {
        teachingMethods.addListListener(new ListListener<TeachingMethod>() {

            public void elementRemoved(ListEvent<TeachingMethod> e) {
                fireTableRowsDeleted(e.getOldIndex(), e.getOldIndex());
            }


            public void elementAdded(ListEvent<TeachingMethod> e) {
                fireTableRowsInserted(e.getNewIndex(), e.getNewIndex());
            }


            public void elementMoved(ListEvent<TeachingMethod> e) {
                // hack. todo: warning: do nothing for now.
            }
        });
    }


    public Collection<FieldGroup<TeachingMethod>> getFieldGroupMap() {
        return Collections.unmodifiableCollection(fieldGroupMap.keySet());
    }


    public int getFieldGroupColumnIndex(FieldGroup<TeachingMethod> fieldGroup) {
        return fieldGroupMap.get(fieldGroup);
    }


    public int getColumnCount() {
        int columnCount = 0;
        for (FieldGroup<TeachingMethod> fieldGroup : fieldGroupMap.keySet())
            columnCount += fieldGroup.size();
        return columnCount;
    }


    @Override
    public Class<?> getColumnClass(int columnIndex) {
        FieldGroup<TeachingMethod> fieldGroup = getFieldGroup(columnIndex);
        int base = fieldGroupMap.get(fieldGroup);
        return fieldGroup.getClass(columnIndex - base);
    }


    @Override
    public String getColumnName(int columnIndex) {
        FieldGroup<TeachingMethod> fieldGroup = getFieldGroup(columnIndex);
        int base = fieldGroupMap.get(fieldGroup);
        return fieldGroup.getFieldName(columnIndex - base);
    }


    public Class<?> getHeaderClass(int columnIndex) {
        FieldGroup<TeachingMethod> fieldGroup = getFieldGroup(columnIndex);
        int base = fieldGroupMap.get(fieldGroup);
        return fieldGroup.getHeaderClass(columnIndex - base);
    }


    public Object getHeader(int columnIndex) {
        FieldGroup<TeachingMethod> fieldGroup = getFieldGroup(columnIndex);
        int base = fieldGroupMap.get(fieldGroup);
        return fieldGroup.getHeader(columnIndex - base);
    }


    /**
     * Get the field group responsible for the given column.
     * 
     * @param columnIndex
     * @return
     * 
     */
    public FieldGroup<TeachingMethod> getFieldGroup(int columnIndex) {
        // todo: cache this info out - is used a *lot*
        FieldGroup<TeachingMethod> lastFieldGroup = null;
        for (Map.Entry<FieldGroup<TeachingMethod>, Integer> entry : fieldGroupMap
                .entrySet()) {
            int index = entry.getValue();
            if (index > columnIndex) {
                return lastFieldGroup;
            }
            lastFieldGroup = entry.getKey();
        }
        return lastFieldGroup;
    }


    public int getRowCount() {
        return teachingMethods.size();
    }


    public Object getColumnTotal(int columnIndex) {
        FieldGroup<TeachingMethod> fieldGroup = getFieldGroup(columnIndex);
        int base = fieldGroupMap.get(fieldGroup);
        return fieldGroup.calculateTotal(columnIndex - base);
    }


    public Object getValueAt(int row, int column) {
        FieldGroup<TeachingMethod> fieldGroup = getFieldGroup(column);
        int base = fieldGroupMap.get(fieldGroup);
        Object object = fieldGroup.getField(row, column - base);
        return object;
    }


    @Override
    public boolean isCellEditable(int row, int column) {
        FieldGroup<TeachingMethod> fieldGroup = getFieldGroup(column);
        int base = fieldGroupMap.get(fieldGroup);
        return fieldGroup.isEditable(row, column - base);
    }


    public void setValueAt(Object value, int row, int column) {
        TeachingMethod teachingMethod = teachingMethods.get(row);
        FieldGroup<TeachingMethod> fieldGroup = getFieldGroup(column);
        int base = fieldGroupMap.get(fieldGroup);
        fieldGroup.setField(teachingMethod, column - base, value);

        // hack to always call this
        // fireTableCellUpdated(row, column);

        // even bigger hack to call this every time as well
        // need to listen to list
        // fireTableCellUpdated(row, 1);

        // hack to call this - is so all values updated appropriately.
        // Need to listen to model really rather than have this here.
        // FieldGroups need listeners.
        fireTableRowsUpdated(row, row);
    }

}
